package com.hup.minicontroller.server.screenImageService;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.media.Image;
import android.media.ImageReader;
import android.util.Log;
import android.view.Display;
import android.view.Surface;

import com.hup.minicontroller.common.framework.SubThreadHandler;
import com.hup.minicontroller.common.util.DisplayUtil;
import com.hup.minicontroller.server.ServerApplication;
import com.hup.minicontroller.server.model.AppState;
import com.hup.minicontroller.server.model.ControlConfig;
import com.hup.minicontroller.server.service.MainService;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;

import lombok.Setter;

/**
 * 使用{@link ImageReader}处理录屏数据
 * https://blog.csdn.net/qq_35928566/article/details/84858924
 * 观察情况:
 * .视频配置最高时会有延迟,否则非常顺畅,基本上无延迟
 * .带虚拟按键区域
 * .支持录制其他app的界面,锁屏,灭屏界面(有些手机灭屏时有指纹图标,也会被显示出来)
 */
public class ImageReaderProcessor implements IScreenDataProcessor {
    private static final String TAG = ImageReaderProcessor.class.getSimpleName();

    /**
     * 没转屏时的分辨率
     */
    private final Point originalScreenSize;
    /**
     * 没转屏时的width/height比例,小于1;
     * 当前算法只考虑默认是竖屏的设备
     */
    private final float originalScale;
    private final AppState appState;
    private final ControlConfig controlConfig;
    private final ImageReader imageReader;
    private final Display display;
    @Setter
    private ScreenDataCallback screenDataCallback;
    /**
     * 上次发送图片的时间,用于限制fps
     */
    private long lastSendTime;

    /**
     * @param screenSize 屏幕没旋转时的分辨率
     */
    @SuppressLint("WrongConstant")//ide识别不了PixelFormat.RGBA_8888???
    public ImageReaderProcessor(Point screenSize) {
        originalScreenSize = screenSize;
        appState = ServerApplication.getBean(MainService.class).getAppState();
        controlConfig = appState.getControlConfig();
        /*
        maxImages:同时处理的图像数量,注解中已经写到:This should be as small as possible to limit memory use
            但无语的是,如果值太低,就一直有刷屏警告wtf:ImageReader_JNI: Unable to acquire a buffer item, very likely client tried to acquire more than maxImages buffers
         */
        imageReader = ImageReader.newInstance(screenSize.x, screenSize.y, PixelFormat.RGBA_8888, 2);
        imageReader.setOnImageAvailableListener(reader -> {
            try (Image image = reader.acquireLatestImage()) {
                if (!controlConfig.isCanEnableScreen() || !controlConfig.isEnableScreen() || !appState.hasClient()) {
                    //不传输图像,即使不使用图像,也必须release图像(放入try中)!否则reader就会一直等待(卡住)
                    return;
                }
                if (image != null) handleImage(image);
            }
        }, new SubThreadHandler("ScreenSubHandler"));//录屏图像的专用处理Handler
        display = DisplayUtil.getDisplay(ServerApplication.getContext());
        originalScale = ((float) originalScreenSize.x) / originalScreenSize.y;
        lastSendTime = System.currentTimeMillis();
    }

    private void handleImage(Image image) {
        //这里的尺寸,总是与imageReader初始化时的尺寸一样的
        int width = image.getWidth();
        int height = image.getHeight();
        int rotation = display.getRotation();//当前设备的方向
        Log.d(TAG, "handleImage: size=" + width + ", " + height + ", rotation=" + rotation);
        //目标图像的缩放比例=当前图像长边的像素/配置的像素
        float targetScale = ((float) controlConfig.getScreenSize()) / Math.max(width, height);

        //解析成Bitmap,代码来自类注释的资料
        final Image.Plane[] planes = image.getPlanes();
        final ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();//像素步幅
        int rowStride = planes[0].getRowStride();//行跨度
        int rowPadding = rowStride - pixelStride * width;
        //算法是固定的,即使转屏后,造成黑边,也不能在此修正?
        //Log.i(TAG, "handleImage: width=" + width + ", pixelStride=" + pixelStride + ", rowStride=" + rowStride + ", rowPadding=" + rowPadding);//
        Bitmap bitmap1 = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
        bitmap1.copyPixelsFromBuffer(buffer);

        /*
        处理图像缩放,设备旋转问题
        输出的结果
            .图像大小符合controlConfig配置
            .屏幕旋转后,图像始终是"正的,没有上下/左右颠倒",纵横分辨率会互换
         */
        Bitmap bitmap2;
        Matrix matrix = new Matrix();
        if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
            //竖屏
            matrix.postScale(targetScale, targetScale);
            /*
            x,y,w,h:指裁剪出原图的局部,纯粹缩放图片则只能用Matrix
            matrix的操作有set，pre和post；set能够直接设置矩阵中的数值；pre类似于矩阵左乘；post类似与矩阵中的右乘?!?!?!?!?
            filter:是否进行滤波处理,用于提升图像质量,优化非平移操作产生的图像(如旋转,缩放)
                因为controlConfig采用输出固定像素的方案,所以图像一定被缩放了,不开filter时有点影响文字阅读
             */
            bitmap2 = Bitmap.createBitmap(bitmap1, 0, 0, width, height, matrix, true);
        } else {
            //横屏
            /*
            处理屏幕左右旋转
                .注意此处只针对Surface.ROTATION_0时是竖屏的设备,如果Surface.ROTATION_0是横屏的,那算法可能是错误的!
                .注意:旧版App使用ScreenShotUtil时,算法是不一样的!!所以不同设备可能有问题
                .bitmap1的图像是上下大幅黑边,左右铺满没黑边,中间是居中的横屏图像(左右旋转时,图像方向都是"正的,没颠倒")
                .所以下面进行裁剪图片
                .此处算法与mediaProjection.createVirtualDisplay传入的屏幕尺寸相关
            已通过测试
                .ROTATION_0是竖屏的设备,app以横屏或竖屏启动后,旋转四个方向(哔哩哔哩app可以测试),都得到正确图像;
             */
            height = (int) (width * originalScale);//居中图像的高度
            int heightPadding = (originalScreenSize.y - height) / 2;
            //Log.i(TAG, "handleImage: originalScale=" + originalScale + ", height=" + height);
            //至此,不用matrix缩放的话,裁剪后图片的大小比例等于[originalScale,即屏幕默认的尺寸比]
            float scale = targetScale / originalScale;//计算传给matrix的比例
            matrix.postScale(scale, scale);
            //裁剪并缩放图像
            bitmap2 = Bitmap.createBitmap(bitmap1, 0, heightPadding, width, height, matrix, true);
        }
        if (bitmap1 != bitmap2) bitmap1.recycle();
        //转byte
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap2.compress(Bitmap.CompressFormat.JPEG, controlConfig.getScreenQuality(), baos);
        width = bitmap2.getWidth();//recycle后获取的话,会有警告日志
        height = bitmap2.getHeight();
        bitmap2.recycle();
        byte[] data = baos.toByteArray();
        int kb = data.length / 1024;
        Log.d(TAG, "handleImage: size=" + width + ", " + height + ", kb=" + kb);

        //按照controlConfig帧率限制通知callback
        if (screenDataCallback != null) {
            long cur = System.currentTimeMillis();
            long diff = cur - lastSendTime;
            int span = 1000 / controlConfig.getFps();
            if (diff >= span) {
                screenDataCallback.handleScreenData(data);
                lastSendTime = cur;
//            } else {
//                Log.i(TAG, "handleImage: 过滤帧率");
            }
        }
    }

    @Override
    public Surface getSurface() {
        return imageReader.getSurface();
    }

    @Override
    public void release() {
        imageReader.close();
    }

}
